package week_08;


import week_07.Queue;
import week_07.Stack;

public class BinaryTree<Key extends Comparable<Key>, Value> {
    /**
     * 记录根节点
     */
    private Node root;
    /**
     * 记录树中元素个数
     */
    private int N;

    private class Node {
        /**
         * 存储键
         */
        public Key key;
        /**
         * 存储值
         */
        private Value value;
        /**
         * 记录左节点
         */
        public Node left;
        /**
         * 记录右节点
         */
        public Node right;

        public Node(Key key, Value value, Node left, Node right) {
            this.key = key;
            this.value = value;
            this.left = left;
            this.right = right;
        }
    }

    /**
     * @return 获取树中元素的个数
     */
    public int size() {
        return N;
    }

    /**
     * 向数中添加元素key-value
     *
     * @param key   当前节点的键
     * @param value 节点的值
     */
    public void put(Key key, Value value) {
        root = put(root, key, value);
    }

    /**
     * 向指定树x上，添加一个键值对，并返回添加后的新树
     *
     * @param x     指定的树
     * @param key   键值对
     * @param value 节点的值
     * @return 添加键值对后返回的新树
     */
    public Node put(Node x, Key key, Value value) {
//        如果x子树为空
        if (x == null) {
            N++;
            return new Node(key, value, null, null);
        }
//        如果x子树不为空
//        比较x节点的键和key的大小
        int cmp = key.compareTo(x.key);
        if (cmp > 0) {
//        如果key大于x节点的键，则继续找x节点的右子树
            x.right = put(x.right, key, value);
        } else if (cmp < 0) {
//        如果key小于x节点的键，则继续寻找x节点的左子树
            x.left = put(x.left, key, value);
        } else {
//        如果key等于x节点的键，则替换x节点的值为value即可
            x.value = value;
        }
        return x;
    }

    /**
     * 从树中找出对应的值
     *
     * @param key 键值对
     * @return 返回的值
     */
    public Value get(Key key) {
        return get(root, key);
    }

    /**
     * 从指定的树x中，找出key对应的值
     *
     * @param x   指定的树
     * @param key 键值对
     * @return 返回的值
     */
    public Value get(Node x, Key key) {
//        x树为null
        if (x == null) {
            return null;
        }
//        x树不为null
        int cmp = key.compareTo(x.key);
        if (cmp > 0) {
//        如果key大于x节点的键，则继续找x节点的右子树
            return get(x.right, key);
        } else if (cmp < 0) {
//        如果key小于x节点的键，则继续寻找x节点的左子树
            return get(x.left, key);
        } else {
//        如果key等于x节点的键，则找到了键为key的节点，只需返回x节点的值即可
            return x.value;
        }
    }

    /**
     * 删除树中key对应的value
     *
     * @param key
     */
    public void delete(Key key) {
        delete(root, key);
    }

    /**
     * 删除指定树x中的可以对应的value，并返回删除后的新树
     *
     * @param x
     * @param key
     * @return
     */
    public Node delete(Node x, Key key) {
//        x树为null
        if (x == null) {
            return null;
        }
//        x树不为null
        int cmp = key.compareTo(x.key);
        if (cmp > 0) {
//        如果key大于x节点的键，则继续找x节点的右子树
            x.right = delete(x.right, key);
        } else if (cmp < 0) {
//        如果key小于x节点的键，则继续寻找x节点的左子树
            x.left = delete(x.left, key);
        } else {
//        如果key等于x节点的键，完成真正的删除节点动作，要删除的就是x
//            首先要找到右子树中最小的键
            if (x.right == null) {
                N--;
                return x.left;
            }
            if (x.left == null) {
                N--;
                return x.right;
            }

            Node minNode = x.right;
            while (minNode.left != null) {
                minNode = minNode.left;
            }
//            删除右子树中最小的节点
            Node n = x.right;
            while (n.left == null) {
                if (n.left.left == null) {
                    n.left = null;
                } else {
//                    变换n节点即可
                    n = n.left;
                }
            }
//            让x节点的左子树成为minNode的左子树
            minNode.left = x.left;
//            让x节点的右子树成为minNode的右子树
            minNode.right = x.right;
//            让x节点的父节点指向minNode
            x = minNode;
            N--;
        }


        return x;
    }

    /**
     * 找出整个树中的最小的键
     *
     * @return 最小键
     */
    public Key min() {
        return min(root).key;
    }

    /**
     * 找出指定树x中最小的键所在的节点
     *
     * @param x 指定的树
     * @return 最小的键所在节点
     */
    public Node min(Node x) {
        if (x.left != null) {
            return min(x.left);
        } else {
            return x;
        }
    }

    /**
     * 找出整个树中的最大的键
     *
     * @return 最大键
     */
    public Key max() {
        return max(root).key;
    }

    /**
     * 找出指定树x中最大的键所在的节点
     *
     * @param x 指定的树
     * @return 最大的键所在节点
     */
    public Node max(Node x) {
        if (x.right != null) {
            return max(x.left);
        } else {
            return x;
        }
    }

    /**
     * 前序遍历
     * 获取整个树中所有的键
     *
     * @return 所有的键
     */
    public Queue<Key> preErgodic() {
        Queue<Key> keys = new Queue<>();
        preErgodic(root, keys);
        return keys;
    }

    public void preErgodic(Node x, Queue<Key> keys) {
        if (x == null) {
            return;
        }
//        把x节点的key放入keys中
        keys.push(x.key);
//        递归遍历x节点的左子树
        if (x.left != null) {
            preErgodic(x.left, keys);
        }
//        递归遍历x节点的右子树
        if (x.right != null) {
            preErgodic(x.right, keys);
        }
    }

    /**
     * 使用中序遍历获取树中所有的键
     *
     * @return
     */
    public Queue<Key> midErgodic() {
        Queue<Key> keys = new Queue<>();
        midErgodic(root, keys);
        return keys;
    }

    /**
     * 使用中序遍历,获取指定树x中所有的键,并存放到keys中
     *
     * @param x    当前树x
     * @param keys 键值对
     */
    public void midErgodic(Node x, Queue<Key> keys) {
        if (x == null) {
            return;
        }
//        先递归把左子树的键放到keys中
        if (x.left != null) {
            midErgodic(x.left, keys);
        }
//        把当前节点x的键放到keys中
        keys.push(x.key);
//        再递归,把右子树中的键放到keys中
        if (x.right != null) {
            midErgodic(x.right, keys);
        }
    }

    /**
     * 使用后序遍历获取树中所有的键
     */
    public Queue<Key> afterErgodic() {
        Queue<Key> keys = new Queue<>();
        afterErgodic(root, keys);
        return keys;
    }

    /**
     * 使用后序遍历,获取指定树x中所有的键,并存放到keys中
     */
    public void afterErgodic(Node x, Queue<Key> keys) {
        if (x == null) {
            return;
        }
//        通过递归把左子树中所有的键放到keys中
        if (x.left != null) {
            afterErgodic(x.left, keys);
        }
//        通过递归把右子树所有的键放到keys中
        if (x.right != null) {
            afterErgodic(x.right, keys);
        }
//        把x节点放入到keys中
        keys.push(x.key);
    }

    /**
     * 层序遍历
     */
    public Queue<Key> layerErgodic() {
//        定义两个队列，分别存储树中的键和树中的节点
        Queue<Key> keys = new Queue<>();
        Queue<Node> nodes = new Queue<>();
//        默认往队列中放入根节点
        nodes.push(root);

        while (!nodes.isEmpty()) {
//            从队列中弹出一个节点，把key放入到keys中
            Node n = nodes.pop();
            keys.push(n.key);
//            判断当前节点还有没有左子节点，如果有则放入到nodes中
            if (n.left != null) {
                nodes.push(n.left);
            }
//            判断当前节点还有没有右子节点，有过有则放入nodes中
            if (n.right != null) {
                nodes.push(n.right);
            }
        }
        return keys;
    }

    //
    public Stack<Value> findAncestor(Key key) {
        Stack<Value> keys = new Stack<>();
        findAncestor(root, keys, key);
        return keys;
    }

    public void findAncestor(Node x, Stack<Value> keys, Key key) {
//        x树为null
        if (x == null) {
            return;
        }
//        x树不为null
        int cmp = key.compareTo(x.key);
        if (cmp > 0) {
            keys.push(x.value);
            findAncestor(x.right, keys, key);
        } else if (cmp < 0) {
            keys.push(x.value);
            findAncestor(x.left, keys, key);
        }
    }

    public Queue<Integer> TreeWidth() {
        Queue<Key> keys = new Queue<>();
        Queue<Node> nodes = new Queue<>();
        Queue<Integer> length = new Queue<>();
        int N = 0;
        int N2 = 0;
        nodes.push(root);
        length.push(nodes.size());
        while (!nodes.isEmpty()) {
            Node n = nodes.pop();
            N = nodes.size();
            N2 = length.pop();
            length.pop();
            if (N > N2) {
                length.push(N);
            } else {
                length.push(N2);
            }
            keys.push(n.key);
            if (n.left != null) {
                nodes.push(n.left);
            }
            if (n.right != null) {
                nodes.push(n.right);
            }
        }
        return length;
    }

    public Integer TreeHeight(Key key) {
        Stack<Value> stack =findAncestor(key);
        int N = stack.size();
        String[] a = new String[N];
        for (int i = 0; i < stack.size(); i++) {
            a[i] = (String) stack.pop();
        }
        a[N-1] = "root";
        return a.length;
    }
}
